﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Util;
using NUnit.Core;
using Microsoft.VisualStudio.TestTools.Common;
using System.IO;
using System.Threading;
using System.Diagnostics;

namespace NUnitForVSTS.NUnitTest
{
    /// <summary>
    /// Synchrones handler for test loader events
    /// </summary>
    public abstract class TestLoaderEventDispatcher : TestEventDispatcher, IDisposable
    {
        private AutoResetEvent _onTestLoaded = new AutoResetEvent(false);
        private AutoResetEvent _onTestFinished = new AutoResetEvent(false);
        private AutoResetEvent _onTestUnloaded = new AutoResetEvent(false);
        private AutoResetEvent _onUnloaded = new AutoResetEvent(false);

        /// <summary>
        /// The tests which are currently in memory
        /// </summary>
        protected IDictionary<string, ITestElement> CurrentTests { get; set; }

        /// <summary>
        /// Gets or sets the loaded location of the Test loader.
        /// </summary>
        /// <value>The loaded location in loader.</value>
        protected string LoadedLocationInLoader
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets or sets the test run id.
        /// </summary>
        /// <value>The test run id.</value>
        protected Guid TestRunId
        {
            get;
            private set;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TestLoaderEventDispatcher"/> class.
        /// </summary>
        /// <param name="testRunId">The test run id.</param>
        protected TestLoaderEventDispatcher(Guid testRunId)
        {
            ServiceManager.Services.AddService(new SettingsService());
            ServiceManager.Services.AddService(new DomainManager());
            ServiceManager.Services.AddService(new ProjectService());
            ServiceManager.Services.AddService(new AddinRegistry());
            ServiceManager.Services.AddService(new AddinManager());
            ServiceManager.Services.AddService(new TestLoader(this));
            ServiceManager.Services.InitializeServices();
            TestRunId = testRunId;
            CurrentTests = new Dictionary<string, ITestElement>();
        }

        /// <summary>
        /// Cleans this instance.
        /// </summary>
        public virtual void Cleanup()
        {
            UnloadAssembly();
            ServiceManager.Services.StopAllServices();
        }

        /// <summary>
        /// Fires the specified handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        /// <param name="e">The <see cref="NUnit.Util.TestEventArgs"/> instance containing the event data.</param>
        /// <remarks>Main communication channel</remarks>
        protected override void Fire(TestEventHandler handler, TestEventArgs e)
        {
            switch (e.Action)
            {
                case TestAction.ProjectLoadFailed:
                case TestAction.ProjectLoaded:
                    break;
                case TestAction.ProjectLoading:
                    break;
                case TestAction.ProjectUnloadFailed:
                case TestAction.ProjectUnloaded:
                    _onUnloaded.Set();
                    break;
                case TestAction.RunFinished:
                    try
                    {
                        if (e.Exception == null)
                        {
                            RunIsFinished(e.Result);
                        }
                        else
                        {
                            RunFinishedWithError(e.Exception);
                        }
                    }
                    finally
                    {
                        _onTestFinished.Set();
                    }
                    break;
                case TestAction.RunStarting:
                    break;
                case TestAction.SuiteFinished:
                    break;
                case TestAction.SuiteStarting:
                    break;
                case TestAction.TestException:
                    break;
                case TestAction.TestFinished:
                    TestIsFinished(e.Result);
                    break;
                case TestAction.TestLoadFailed:
                case TestAction.TestLoaded:
                    try
                    {
                        TestIsLoaded(e.Test);
                    }
                    finally
                    {
                        _onTestLoaded.Set();
                    }
                    break;
                case TestAction.TestLoading:
                    break;
                case TestAction.TestOutput:
                    WriteOutput(e.TestOutput);
                    break;
                case TestAction.TestReloadFailed:
                    break;
                case TestAction.TestReloaded:
                    break;
                case TestAction.TestReloading:
                    break;
                case TestAction.TestStarting:
                    TestIsStarted(e.TestName);
                    break;
                case TestAction.TestUnloadFailed:
                case TestAction.TestUnloaded:
                    try
                    {
                        TestIsUnloaded();
                    }
                    finally
                    {
                        _onTestUnloaded.Set();
                    }
                    break;
                case TestAction.TestUnloading:
                case TestAction.ProjectUnloading:
                    break;
                default:
                    break;
            }
        }


        /// <summary>
        /// Tests the started.
        /// </summary>
        /// <param name="testName">Name of the test.</param>
        protected virtual void TestIsStarted(TestName testName)
        {
        }

        /// <summary>
        /// Runs the finished with error.
        /// </summary>
        /// <param name="exception">The exception.</param>
        protected virtual void RunFinishedWithError(Exception exception)
        {
        }
        /// <summary>
        /// Run when the run is finished
        /// </summary>
        /// <param name="result">The result.</param>
        protected virtual void RunIsFinished(NUnit.Core.TestResult result)
        {
            if (result != null && result.Results != null)
            {
                foreach (NUnit.Core.TestResult childResult in result.Results)
                {
                    RunIsFinished(childResult);
                }
            }
        }

        /// <summary>
        /// Executes when the test is loaded
        /// </summary>
        protected virtual void TestIsLoaded(ITest test)
        {
        }

        /// <summary>
        /// Executes when the test is loaded
        /// </summary>
        protected virtual void TestIsUnloaded()
        {
        }
        /// <summary>
        /// Writes the output to the log.
        /// </summary>
        /// <param name="output">The output.</param>
        protected virtual void WriteOutput(TestOutput output)
        {
        }

        /// <summary>
        /// Test is finshed
        /// </summary>
        /// <param name="result">The nunit test result.</param>
        protected virtual void TestIsFinished(NUnit.Core.TestResult result)
        {
        }


        /// <summary>
        /// Loads the assembly in loader.
        /// </summary>
        /// <param name="location">The location.</param>
        protected void LoadAssemblyInLoader(string location)
        {
            if (!string.Equals(LoadedLocationInLoader, location, StringComparison.OrdinalIgnoreCase))
            {
                UnloadAssembly();
                LoadAssembly(location);
            }
        }

        /// <summary>
        /// Loads the assembly.
        /// </summary>
        /// <param name="location">The location.</param>
        private void LoadAssembly(string location)
        {
            string config = GetConfigFilename(location);

            if (string.IsNullOrEmpty(config))
            {
                Services.TestLoader.NewProject();
                Services.TestLoader.LoadProject(location);
            }
            else
            {
                Services.TestLoader.NewProject();
                Services.TestLoader.LoadProject(location, config);
            }
            LoadedLocationInLoader = location;
        }

        /// <summary>
        /// Unloads the assembly.
        /// </summary>
        private void UnloadAssembly()
        {
            if (LoadedLocationInLoader != null && Services.TestLoader.IsProjectLoaded)
            {
                Services.TestLoader.UnloadProject();
                _onUnloaded.WaitOne();
            }
        }

        #region Loader actions
        /// <summary>
        /// Gets the config filename.
        /// </summary>
        /// <param name="fullPath">The full path.</param>
        /// <returns></returns>
        private static string GetConfigFilename(string fullPath)
        {
            string configFile = fullPath + ".config";
            if (File.Exists(configFile))
            {
                return configFile;
            }
            return null;
        }

        /// <summary>
        /// Unloads the tests.
        /// </summary>
        protected void UnloadTests()
        {
            Services.TestLoader.UnloadTest();
            _onTestUnloaded.WaitOne();
        }
        /// <summary>
        /// Loads the test.
        /// </summary>
        /// <param name="testClassName">Name of the test class.</param>
        protected void LoadTest(string testClassName)
        {
            if (string.IsNullOrEmpty(testClassName))
            {
                Services.TestLoader.LoadTest();
            }
            else
            {
                Services.TestLoader.LoadTest(testClassName);
            }
            _onTestLoaded.WaitOne();
        }
        /// <summary>
        /// Ares the tests.
        /// </summary>
        /// <returns></returns>
        protected static bool AreTests()
        {
            return Services.TestLoader.IsTestLoaded;
        }
        /// <summary>
        /// Runs the tests.
        /// </summary>
        /// <param name="executionFilter">The execution filter.</param>
        protected void RunTests(ITestFilter executionFilter)
        {
            Services.TestLoader.RunTests(executionFilter);
            _onTestFinished.WaitOne();
        }
        /// <summary>
        /// Cancels the tests.
        /// </summary>
        public static void CancelTests()
        {
            Services.TestLoader.CancelTestRun();
        }
        #endregion
        #region IDisposable
        private bool _disposed;
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (!(this._disposed))
            {
                if (disposing)
                {
                    _onTestLoaded.Close();
                    _onTestFinished.Close();
                    _onTestUnloaded.Close();
                    _onUnloaded.Close();
                }
            }
            this._disposed = true;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="TestLoaderEventDispatcher"/> is reclaimed by garbage collection.
        /// </summary>
        ~TestLoaderEventDispatcher()
        {
            Dispose(false);
        }
        #endregion
    }
}